<?php

namespace Core\Server;

use Core\Common\Base\Config;
use Core\Common\Base\ConfigSource;
use Core\Common\Base\Log;
use Core\Common\Base\Service;

class CrontabServer
{
    const BASE_PATH = CORE_APP_PATH . 'Cron';

    // 定时任务的名字
    public $cronDesc = "default";
    public static $isNeedExit = false;

    // 任务开始时间
    protected $startTime = 0;

    /**
     * 从consul里加载的配置文件信息
     * @var \Swoole\Table
     */
    private $appConfig = null;

    /**
     * 定时任务初始化
     * CrontabServer constructor.
     */
    public function __construct()
    {
        $this->startTime = microtime(true);
        register_shutdown_function([$this, 'onFatalError']);

        // 定义当前运行环境为crontab
        define("CORE_RUNTIME", "crontab");

        // 初始化服务发现缓存表
        Service::serviceBootstrap();
        if (ConfigSource::isUseConsul()) {
            $configInitRet = ConfigSource::initTableAndInitConfig();
            if (!$configInitRet["ok"]) {
                Log::coreLog([
                    "msg" => "fetch appConfig error, " . $configInitRet["msg"],
                    "time" => date("Y-m-d H:i:s"),
                ]);
                exit();
            } else {
                $this->appConfig = $configInitRet["table"];
                Log::coreLog(["load config from consul success, ", time(),]);
            }
        }
        Config::init();
    }

    /**
     * 捕获信号，优雅退出
     */
    public function catchSuperVisorSign()
    {
        pcntl_signal(SIGTERM, function ($signo) {
            switch ($signo) {
                case SIGTERM:
                    // 处理SIGTERM信号
                    echo "get supervisor signo ... \n";
                    self::$isNeedExit = true;
                    return true;
            }
        });
    }

    /**
     * 是否需要优雅退出程序
     * @return bool
     */
    public static function needExit()
    {
        return self::$isNeedExit;
    }

    public function __set($name, $value)
    {
        $this->$name = $value;
    }

    public function onFatalError()
    {
        $error = error_get_last();
        if (empty($error)) {
            return;
        }
        $this->error(['error_type' => 'fatal'], $error);
    }

    public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext)
    {
        try {
            throw new \Exception;
        } catch (\Throwable $exc) {
            $detail = [
                'error_type' => 'error',
                'error_source' => $errfile . ':' . $errline,
                'query' => [],
            ];
            $context = [
                'error_code' => $errno,
                'message' => $errstr,
                'trace' => $exc->getTrace(),
            ];
            $this->error($detail, $context);
        }
        return true;
    }

    protected function log(...$values)
    {
        Log::cron(LOG_INFO, ['class' => static::class], ...$values);
    }

    protected function error(...$values)
    {
        Log::cron(LOG_ERR, ['class' => static::class], ...$values);
    }

    public static function jobList()
    {
        $data = [];
        self::readdir(self::BASE_PATH, $data);
        return $data;
    }

    private static function readdir($path, &$data = [])
    {
        $ret = [];
        $handle = opendir($path);
        if (!$handle) {
            return $ret;
        }
        $namespace = '\App\Cron\\' . str_replace([self::BASE_PATH, DIRECTORY_SEPARATOR], ['', '\\'], $path) . '\\';
        while (false !== ($entry = readdir($handle))) {
            if ('.' == $entry || '..' == $entry) {
                continue;
            }
            if (is_file($path . DIRECTORY_SEPARATOR . $entry)) {
                $classname = trim($namespace, '\\') . '\\' . substr($entry, 0, -4);
                if (!class_exists($classname)) {
                    continue;
                }
                $data[strtolower(substr($entry, 0, -4))] = $classname;
                continue;
            }
            self::readdir($path . DIRECTORY_SEPARATOR . $entry, $data);
        }
        closedir($handle);
    }

    public static function dispatch($className)
    {
        $obj = new $className();
        $obj->run();
    }

    public function usage()
    {
        return "Usage: php " . __FILE__ . " [list|run] <jobname>\n";
    }

    public function run()
    {
        global $argc, $argv;
        if ($argc < 2) {
            $ret = $this->usage();
            exit($ret);
        }
        $command = $argv[1];
        switch ($command) {
            case 'list':
                $jobs = self::jobList();
                echo "Job list:\n";
                $i = 0;
                foreach ($jobs as $k => $v) {
                    echo $i . "\t" . $k . "\n";
                    ++$i;
                }
                echo "\n";
                break;
            case 'run':
                if ($argc < 3) {
                    exit("Usage: php " . __FILE__ . " run <jobname>\n");
                }
                $jobname = strtolower(str_replace('_', '', $argv[2]));
                $jobs = self::jobList();
                if (!isset($jobs[$jobname])) {
                    exit("job {$jobname} not found\n");
                }
                $cls = $jobs[$jobname];
                if (!class_exists($cls)) {
                    exit("class {$cls} not found\n");
                }
                self::dispatch($cls);
                break;
            default:
                exit("Usage: php " . __FILE__ . "[list|run] <jobname>\n");
                break;
        }
    }
}
